二叉树应用篇

二叉查找树(Binary Search Tree)

二叉查找树:在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。也称为二叉搜索树。

中序遍历二叉查找树,可以输出有序的数据序列,时间复杂度是 O(n),非常高效。因此,二叉查找树也叫作二叉排序树。

二叉查找树的操作时间复杂度其实都跟树的高度成正比,也就是 O(height)。平衡二叉查找树的高度接近 logn,所以插入、删除、查找操作的时间复杂度也比较稳定,是 O(logn)。

查找

我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。

public Node find(int data) { 
    Node p = tree; 
    while (p != null) { 
        if (data < p.data) p = p.left; 
        else if (data > p.data) p = p.right; 
        else return p; 
    }
    return null; 
}

插入

如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。

public void insert(int data) {
  if (tree == null) {
    tree = new Node(data);
    return;
  }

  Node p = tree;
  while (p != null) {
    if (data > p.data) {
      if (p.right == null) {
        p.right = new Node(data);
        return;
      }
      p = p.right;
    } else { // data < p.data
      if (p.left == null) {
        p.left = new Node(data);
        return;
      }
      p = p.left;
    }
  }
}

删除

  1. 第一种情况是,如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null;
  2. 第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了;
  3. 第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了)。

public void delete(int data) {
  Node p = tree; // p指向要删除的节点,初始化指向根节点
  Node pp = null; // pp记录的是p的父节点
  while (p != null && p.data != data) {
    pp = p;
    if (data > p.data) p = p.right;
    else p = p.left;
  }
  if (p == null) return; // 没有找到

  // 要删除的节点有两个子节点
  if (p.left != null && p.right != null) { // 查找右子树中最小节点
    Node minP = p.right;
    Node minPP = p; // minPP表示minP的父节点
    while (minP.left != null) {
      minPP = minP;
      minP = minP.left;
    }
    p.data = minP.data; // 将minP的数据替换到p中
    p = minP; // 下面就变成了删除minP了
    pp = minPP;
  }

  // 删除节点是叶子节点或者仅有一个子节点
  Node child; // p的子节点
  if (p.left != null) child = p.left;
  else if (p.right != null) child = p.right;
  else child = null;

  if (pp == null) tree = child; // 删除的是根节点
  else if (pp.left == p) pp.left = child;
  else pp.right = child;
}

平衡二叉树

平衡二叉树:二叉树中任意一个节点的左右子树的高度相差不能大于 1。从这个定义来看,上一节我们讲的完全二叉树、满二叉树其实都是平衡二叉树,但是非完全二叉树也有可能是平衡二叉树。

发明平衡二叉查找树这类数据结构的初衷是,解决普通二叉查找树在频繁的插入、删除等动态更新的情况下,出现时间复杂度退化的问题。

红黑树

红黑树的英文是“Red-Black Tree”,简称 R-B Tree。它是一种不严格的平衡二叉查找树。它的定义是不严格符合平衡二叉查找树的定义的。

红黑树中的节点,一类被标记为黑色,一类被标记为红色。除此之外,一棵红黑树还需要满足这样几个要求:

  • 根节点是黑色的;
  • 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据;
  • 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的;
  • 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;

在红黑树中,红色节点不能相邻,也就是说,有一个红色节点就要至少有一个黑色节点,将它跟其他红色节点隔开。红黑树中包含最多黑色节点的路径不会超过 log2n,所以加入红色节点之后,最长路径不会超过 2log2n,也就是说,红黑树的高度近似 2log2n。所以,红黑树的高度只比高度平衡的 AVL 树的高度(log2n)仅仅大了一倍,在性能上,下降得并不多。这样推导出来的结果不够精确,实际上红黑树的性能更好。

哈夫曼树

哈夫曼树:在含有 n 个带权叶节点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称为最优二叉树。哈夫曼树也不唯一。

节点的带权路径长度 Weighted Path Length(缩写 WPL):节点的带权路径长度表示从根节点到该节点的路径长度与该节点权值的乘积。

树的带权路径长度:树的带权路径长度是树中所有叶子节点的带权路径长度之和。

如果给定 n 个叶子节点,构造包含这 n 个叶子节点的哈夫曼树的步骤如下:

  • 将这 n 个节点分别作为 n 棵仅含一个节点(根节点)的二叉树,这样就构成了一个二叉树集合 F;
  • 构造一个新节点,在集合 F 中选取两棵根节点权值最小的树作为这个新节点的左右子树,这里谁左谁右顺序任意,以此构造一棵新二叉树。这个新节点的权值是左右两棵子树根节点的权值之和;
  • 从集合 F 中删除刚刚选取的两棵树,并将新得到的树加入到集合 F 中。 重复上面的 2 个步骤,直到集合 F 中只剩下一棵树为止,这棵树就是哈夫曼树。
Copyright © qingeneral.github.io 2023 all right reserved,powered by Gitbook该文章修订时间: 2023-07-30 15:31:52

results matching ""

    No results matching ""